package com.wdl.datarest.implementation;

import java.net.DatagramPacket;
import java.util.Date;

import com.wdl.datarest.data.DeviceRepository;
import com.wdl.datarest.data.PersonRepository;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class DevMonTcpServerHandler extends ChannelInboundHandlerAdapter {

	private DevCnfgCache devCnfgCache;

	private PersonRepository personRepo;

	private InfluxDBData influxDBData;

	private DeviceRepository deviceRepo;

	private DevWorkerThread[] devDataThreads;

	public DevMonTcpServerHandler(DevCnfgCache _devCache, PersonRepository _personRepo, InfluxDBData _influxDBData,
			DeviceRepository _deviceRepo, DevWorkerThread[] _devDataThreads) {
		this.devCnfgCache = _devCache;
		this.personRepo = _personRepo;
		this.influxDBData = _influxDBData;
		this.deviceRepo = _deviceRepo;
		this.devDataThreads = _devDataThreads;
	}

	/**
	 * 客户端与服务端创建连接的时候调用
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("客户端与服务端连接开始...");
	}

	/**
	 * 客户端与服务端断开连接时调用
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("客户端与服务端连接关闭...");

		String sessionId = NettyConfig.getSessionId(ctx.channel());

		NettyConfig.remove(sessionId);
	}

	/**
	 * 服务端接收客户端发送过来的数据结束之后调用
	 */
	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
		// System.out.println("信息接收完毕...");
	}

	/**
	 * 工程出现异常的时候调用
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}

	/**
	 * 这里接收了客户端发来的信息
	 */
	@Override
	public void channelRead(ChannelHandlerContext channelHandlerContext, Object info) throws Exception {
		ByteBuf buf = (ByteBuf) info;
		System.out.println("接收客户端数据:" + ByteBufUtil.hexDump(buf) + "----" + new Date().toString());
		byte[] req = new byte[buf.readableBytes()];
		buf.readBytes(req);
		try {
			int threadId;
			DatagramPacket packet = new DatagramPacket(req, req.length);

			byte[] tmp = packet.getData();
			byte[] byteDevId = new byte[10];
			for (int i = 0; i < 10; i++) {
				byteDevId[i] = (byte) tmp[4 + i];
			}
			String devId = new String(byteDevId, "UTF-8");
			System.out.println("设备id：" + devId);

			synchronized (this) {
				threadId = computeThreadId(devId);

				int waitTimes = 0;
				while (devDataThreads[threadId].isQueueFull() && (waitTimes < 10)) {

					Thread.sleep(100);
					waitTimes++;
				}

				devDataThreads[threadId].add(packet);
			}
			CacheData cacheData = devCnfgCache.getCnfgDataFromCacheByDeviceIdString(devId);

			boolean syncedToDev = true;
			if (cacheData != null) {
				syncedToDev = cacheData.getSyncedToDev();
			}

			if (syncedToDev == false) {
				cacheData.setSyncedToDev(true);
				devCnfgCache.updateCacheByDeviceIdString(devId, cacheData);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void sendResponseToDev(DatagramPacket packet, ChannelHandlerContext channelHandlerContext) {
		byte[] bytePacket = new byte[25];
		byte[] byteResponse = new byte[25];
		byte[] byteDevId = new byte[10];

		bytePacket = packet.getData();

		// In device data the device ID starts from 5th and 10 bytes long.
		for (int i = 0; i < 10; i++) {
			byteDevId[i] = bytePacket[4 + i];
		}

		String strDevId = new String(byteDevId, 0, byteDevId.length);
		CacheData cacheData = devCnfgCache.getCnfgDataFromCacheByDeviceIdString(strDevId);

		boolean syncedToDev = true;
		if (cacheData != null) {
			syncedToDev = cacheData.getSyncedToDev();
		}

		/*
		 * When data is received, a response to device is always needed.
		 * Response can be a simple ACK, or device config data.
		 */
		// 1. Header: always 0xE5
		byteResponse[0] = (byte) 0xE5;

		if (false == syncedToDev) {
			// 2. Type: 0xF1(config data), 0xF2(reserved), 0xF3(ACK)
			byteResponse[1] = (byte) 0xF1;
			// 3. Frame number
			byteResponse[2] = bytePacket[2];
			// 4. Data length
			byteResponse[3] = 7;
			// 5. Data
			if (cacheData.getEnableUpAlarm() == true) {
				byteResponse[4] = (byte) 0xC1;
			} else {
				byteResponse[4] = (byte) 0x0;
			}
			if (cacheData.getEnableEdgeAlarm() == true) {
				byteResponse[5] = (byte) 0xC2;
			} else {
				byteResponse[5] = (byte) 0x0;
			}
			if (cacheData.getEnableAwayAlarm() == true) {
				byteResponse[6] = (byte) 0xC3;
			} else {
				byteResponse[6] = (byte) 0x0;
			}
			byteResponse[7] = (byte) cacheData.getHbUpperTH();
			byteResponse[8] = (byte) cacheData.getHbLowerTH();
			byteResponse[9] = (byte) cacheData.getBrUpperTH();
			byteResponse[10] = (byte) cacheData.getBrLowerTH();
			// 6. CRC
			byte[] byteCRCMsg = new byte[10];
			for (int i = 0; i < byteCRCMsg.length; i++) {
				byteCRCMsg[i] = byteResponse[i + 1];
			}
			short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
			byte[] byteCRC = short2bytes(shortCRC);
			byteResponse[11] = byteCRC[1];
			byteResponse[12] = byteCRC[0];
			// 7. Tail
			byteResponse[13] = (byte) 0xE6;
		} else {
			// 2. Type: 0xF1(config data), 0xF2(reserved), 0xF3(ACK)
			byteResponse[1] = (byte) 0xF3;
			// 3. Frame number
			byteResponse[2] = bytePacket[2];
			// 4. Data length
			byteResponse[3] = 1;
			// 5. Data
			byteResponse[4] = (byte) 0xE4;
			// 6. CRC
			byte[] byteCRCMsg = new byte[4];
			for (int i = 0; i < byteCRCMsg.length; i++) {
				byteCRCMsg[i] = byteResponse[i + 1];
			}
			short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
			byte[] byteCRC = short2bytes(shortCRC);
			byteResponse[5] = byteCRC[1];
			byteResponse[6] = byteCRC[0];
			// 7. Tail
			byteResponse[7] = (byte) 0xE6;
		}

		// send the response to the device at "address" and "port"
		ByteBuf pingMessage = Unpooled.buffer();
		pingMessage.writeBytes(byteResponse);

		System.out.println("发送数据:" + ByteBufUtil.hexDump(pingMessage) + "----" + new Date().toString());
		channelHandlerContext.channel().writeAndFlush(pingMessage);

		if (syncedToDev == false) {
			cacheData.setSyncedToDev(true);
			devCnfgCache.updateCacheByDeviceIdString(strDevId, cacheData);
		}
	}

	public static short getCrc16(byte[] Msg, int length) {
		int CRC = 0xffff;
		int wCPoly = 0x1021;
		int wChar = 0;
		int i = 0;

		while (i < length) {
			wChar = Msg[i];
			CRC ^= (wChar << 8);

			for (int count = 0; count < 8; count++) {
				if ((CRC & 0x8000) != 0) {
					CRC = (CRC << 1) ^ wCPoly;
				} else {
					CRC = CRC << 1;
				}
			}
			i++;
		}
		return (short) CRC;
	}

	public static byte[] short2bytes(short s) {
		byte[] bytes = new byte[2];
		for (int i = 0; i < 2; i++) {
			bytes[i] = (byte) (s % 256);
			s >>= 8;
		}
		return bytes;
	}

	private int computeThreadId(String devId) {
		// log.info("devId is: " + devId + ", devDataThreads.length is: " +
		// devDataThreads.length + ", hash is: " + Math.abs(devId.hashCode()));
		// return Math.abs(devId.hashCode()%100) % devDataThreads.length;
		return Math.abs(devId.hashCode()) % devDataThreads.length;
	}

	public static void printPacket(byte[] bArray) {
		int len = bArray.length;
		String strOutput = "";
		for (int i = 0; i < len; i++) {
			strOutput = strOutput + String.format("%02X ", bArray[i]);
		}
	}

}